<?php

abstract class BaseRepository2
{
	protected $mapper;

	/**
	 * @var DomainModel
	 */
	protected $modelObject;

	protected $identityMap = array();
	protected $useIdentityMap = true;
	protected $mustStoreAll = false;
	protected $allLoaded = false;

	private function __clone()
	{}

	public function setMapper($mapper)
	{
		$this->mapper = $mapper;
	}

	/**
	 * @return MapperBase
	 */
	protected function getMapper()
	{
		return $this->mapper;
	}
	
	public function deactivateIdentityMapUsage()
	{
		$this->useIdentityMap = false;
	}

	public function getById($ID)
	{
		if ($this->mustStoreAll && !$this->allLoaded && $this->useIdentityMap)
		{
			$this->getAll();
		}

		if (!$this->useIdentityMap)
		{
			return $this->getMapper()->getById($ID);
		}
		else
		{
			if (!isset($this->identityMap[$ID]))
			{
				$this->identityMap[$ID] = $this->getMapper()->getById($ID);
			}
			return $this->identityMap[$ID];
		}
	}

	public function getByIds($IDs)
	{
		if ($this->mustStoreAll && !$this->allLoaded && $this->useIdentityMap)
		{
			$this->getAll();
		}

		if (!$this->useIdentityMap)
		{
			return $this->getMapper()->getByIds($IDs);
		}
		else
		{
			$allElements = array();
			foreach($IDs as $ID)
			{
				$allElements[] = $this->getById($ID);
			}
			return $allElements;
		}
	}

    public function getByListId($QB_listID)
    {
        $criteria = new Criteria("QB_ListID", Criteria::EQUAL, $QB_listID);
        return $this->findFirstByCriterias($criteria);
    }

	public function getAll()
	{
		if ($this->allLoaded && $this->useIdentityMap)
		{
			$all = $this->identityMap;
		}
		else
		{
			$all = $this->getMapper()->getAll();
			if ($this->mustStoreAll)
			{
				foreach ($all as $entity)
				{
					$ID = $entity->{$this->modelObject->getIdField()};
					$this->identityMap[$ID] = $entity;
				}
				$this->allLoaded = true;
			}
		}
		return $all;
	}

	public function findByCriterias($criterias, $order = '')
	{
		return $this->getMapper()->findByCriterias($criterias, $order);
	}

	public function findFirstByCriterias($criterias, $order = '')
	{
		$entities = $this->findByCriterias($criterias, $order);
		if (count($entities) > 0)
		{
			return array_shift($entities);
		}
        else
        {
            return null;
        }
	}

	public function delete($entity)
	{
		$ID = $entity->{
			$this->modelObject->getIdField()}
		;
		if ($ID > 0)
		{
			return $this->deleteById($ID);
		}
	}

	public function deleteById($ID)
	{
		$dependencies = $this->getDependenciesById($ID);
		if (empty($dependencies))
		{
			if ($this->getMapper()->deleteById($ID))
			{
				unset($this->identityMap[$ID]);
				$this->deleteDependents($ID);
				return "SUCCESS";
			}
		}
		else
		{
			return "ERROR_".strtolower(array_shift($dependencies));
		}
	}

    public function deleteByListId($QB_listID)
    {
        $entity = $this->getByListId($QB_listID);
        return $this->delete($entity);
    }

	protected function getDependenciesById($ID)
	{
		$dependencies = array();

		foreach ($this->modelObject->getHasMany() as $has)
		{
			if (!$has["dependent"])
			{
				$criteria = new Criteria($has["foreignKey"], Criteria::EQUAL, $ID);
				if ($mapper = ServiceLocator::getInstance()->getMapperByClassName($has["className"]))
				{
					if ($mapper->countByCriterias($criteria) > 0)
					{
						$dependencies[] = $has["className"];
					}
				}
				else
				{
					/* A effacer lorsque toutes les classes auront un repository */
					try
					{
						$reflectionClassMapper = new ReflectionClass($has["className"]);
						$object = $reflectionClassMapper->newInstance();

						if (count($object->findByCriterias($criteria)) > 0)
						{
							$dependencies[] = $has["className"];
						}
					} /* laisser l'exception apres migration de toutes les classes */
					catch (Exception $e)
					{
						throw new ServerException(5001, '', "Erreur dans getHasMany() de la classe ".get_class($this->modelObject).". RepositoryByClassName(".$has["className"].") n'existe pas.", true, true);
					}
				}
			}
		}
		return $dependencies;
	}

	protected function deleteDependents($ID)
	{
		foreach ($this->modelObject->getHasMany() as $has)
		{
			if ($has["dependent"])
			{
				$criteria = new Criteria($has["foreignKey"], Criteria::EQUAL, $ID);
				if ($mapper = ServiceLocator::getInstance()->getMapperByClassName($has["className"]))
				{
					if ($has["exclusive"])
					{
						$mapper->deleteByCriterias($criteria);
					}
					else
					{
						$indexKey = $mapper->getIndexKeyField();
						$dependents = $mapper->findByCriterias($criteria);
						foreach ($dependents as $dependent)
						{
							$mapper->deleteById($dependent->{$indexKey});
						}
					}
				}
				else
				{
					//TODO: A effacer lorsque toutes les classes auront un repository
					try
					{
						$reflectionClassMapper = new ReflectionClass($has["className"]);
						$object = $reflectionClassMapper->newInstance();

						$dependents = $object->findByCriterias($criteria);
						foreach ($dependents as $dependent)
						{
							$dependent->Erase();
						}
					} /* laisser l'exception apres migration de toutes les classes */
					catch (Exception $e)
					{
						throw new ServerException(5002, '', "Erreur dans getHasMany() de la classe ".get_class($this->modelObject).". RepositoryByClassName(".$has["className"].") n'existe pas.", true, true);
					}
				}
			}
		}
		foreach ($this->modelObject->getHasOne() as $has)
		{
			if ($has["dependent"])
			{
				$criteria = new Criteria($has["foreignKey"], Criteria::EQUAL, $ID);
				$repo = ServiceLocator::getInstance()->getRepositoryByClassName($has["className"]);
				$repo->deleteByCriterias($criteria);
			}
		}
	}

	public function deleteByCriterias($criterias)
	{
		return $this->getMapper()->deleteByCriterias($criterias);
	}

	public function deleteAll()
	{
		if ($this->getMapper()->deleteAll())
		{
			$this->identityMap = array();
			return true;
		}
	}

	public function saveAll(&$entities)
	{
		foreach($entities as $entity)
		{
			$this->save($entity);
		}
	}

	public function save(&$entity)
	{
		$entity->validMeBeforeSave();
		$this->getMapper()->save($entity);

		$this->identityMap[$entity->{
			$this->modelObject->getIdField()}
		] = $entity;
		return $entity;
	}

    public function flagAsExportedToAccounting($entity)
    {
        if (get_class($this->modelObject) == "Bill" || get_class($this->modelObject) == "Receipt" || get_class($this->modelObject) == "Credit" || get_class($this->modelObject) == "Refund")
        {
            if ($entity->IDEXPORT_SEQUENCE < 1)
            {
                $entity->IDEXPORT_SEQUENCE = 1;
                $this->getMapper()->updateByCriterias(array("IDEXPORT_SEQUENCE" => "1"), new Criteria($this->getMapper()->getIndexKeyField(), Criteria::EQUAL, $entity->{$this->getMapper()->getIndexKeyField()}));
            }
        }
    }

	public function countByCriterias($criterias)
	{
		return $this->getMapper()->countByCriterias($criterias);
	}

	public function replace($ID, $newID)
	{
		if ($newID > 0)
		{
			foreach ($this->modelObject->getHasMany() as $hasMany)
			{
				$fieldsToUpdate = array($hasMany["foreignKey"] => $newID);
				$criteria = new Criteria($hasMany["foreignKey"], Criteria::EQUAL, $ID);
				
				if ($mapper = ServiceLocator::getInstance()->getMapperByClassName($hasMany["className"]))
				{
					$mapper->updateByCriterias($fieldsToUpdate, $criteria);
				}
				else
				{
					// TODO: A effacer lorsque toutes les classes auront un repository */
					try 
					{
						$reflectionClassMapper = new ReflectionClass($hasMany["className"]);
						$object = $reflectionClassMapper->newInstance();

						$dependents = $object->findByCriterias($criteria);
						foreach ($dependents as $dependent)
						{
							$dependent->{$hasMany["foreignKey"]} = $newID;
							$dependent->Save();
						}
					}
					
					/* laisser l'exception apres migration de toutes les classes */
					catch (Exception $e)
					{
						throw new ServerException(5003,
						'',
						"Erreur dans getHasMany() de la classe ".get_class($this->modelObject).". RepositoryByClassName(".$hasMany["className"].") n'existe pas.", true, true);
					}
				}
			}
		}
		return true;
	}

	public function updateFieldForEntity( $fieldName, $fieldValue, $entityID )
	{
		return $this->getMapper()->updateFieldForEntity($fieldName, $fieldValue, $entityID);
	}

	public function updateByCriterias($fieldsToUpdate, $criterias)
	{
		return $this->getMapper()->updateByCriterias($fieldsToUpdate, $criterias);
	}
	
	/**
	 * Format variable according to ObjectCollection editable mode in application.
	 * For non editable ObjectCollection (which is shown as a ComboBox in UI) name is not modified to avoid list item duplication
	 * @param string The variable to format
	 * @param string the ObjectCollection name as used in webservice->getStringCollection
	 * @return string The formated value
	 */
	protected function formatName( $name, $type )
	{
		$nonEditableCollections = ServiceLocator::getInstance()->getEtablishment()->non_editable_collection_names;
		if( strpos($nonEditableCollections, $type) !== false )
		{
			return $name;
		}
		return trim(ucwords(strtolower($name)));
	}

    protected function findByName( $field, $value )
    {
        foreach ($this->getAll() as $object)
        {
            if ($object->{$field} == $value)
            {
                return $object;
            }
        }
        return null;
    }

	/**
	 * @param SearchQuery $searchQuery
	 */
	public function getFromQuery( $searchQuery )
	{
		return $this->getMapper()->searchFromQuery($searchQuery);
	}

}
